package com.apes.framework.jpa;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.apes.framework.api.SpringManager;
import com.apes.framework.jpa.audit.Auditing;
import com.apes.framework.jpa.comment.Comment;
import com.apes.framework.jpa.tuple.date.DateTime;
import com.apes.framework.plugin.soe.annotations.States;
import com.apes.framework.plugin.soe.metadata.BusinessType;
import com.apes.framework.plugin.soe.store.database.repository.BusinessTypeRepository;
import com.apes.framework.util.DateUtil;
import com.apes.framework.util.JpaUtil;
import com.apes.framework.util.MapUtil;
import com.apes.framework.util.ReflectUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.GenerationTime;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.io.Serializable;
import java.util.*;

/**
 * 功能：
 *
 * @author xul
 * @create 2018-04-10 11:46
 */
@Slf4j
@Getter
@Setter
@MappedSuperclass
public class BaseModel implements ApesBean {

    @JSONField(format = DateUtil.SHOW_DATETIME_FORMAT)
    @DateTime(GenerationTime.INSERT)
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_date", updatable = false)
    @Comment("创建时间")
    private Date createDate;

    @JSONField(format = DateUtil.SHOW_DATETIME_FORMAT)
    @DateTime
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "update_date", insertable = false)
    @Comment("修改日期")
    private Date updateDate;

    @JSONField(format = DateUtil.SHOW_DATETIME_MILLI_FORMAT)
    @DateTime
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "last_updated_stamp")
    @Comment("时间戳")
    private Date lastUpdatedStamp;

    @JSONField(serialize = false)
    @Transient
    private boolean write = false;

    @JSONField(serialize = false)
    @Transient
    private boolean load = false;

    @Transient
    private String innerBusinessKey; //ID

    public String getInnerBusinessKey() {
        return JpaUtil.getIdToStr(this);
    }

    @Transient
    @JSONField(serialize = false)
    private String action; //事务操作

    @Transient
    @Comment("会话ID")
    private String sessionId;

    @Transient
    @Comment("审计内容")
    @JSONField(serialize = false)
    private String audit;

    public String getBusinessTypeName(String fieldName) {
        String value = ReflectUtil.getFieldValue(this, fieldName);
        if (StringUtils.isEmpty(value)) return null;
        BusinessType businessType = SpringManager.getBean(BusinessTypeRepository.class).findOne(value);
        if (businessType == null) return null;
        return businessType.getName();
    }

    /**
     * 审计
     **/
    public void audit() {
        Auditing auditing = ReflectUtil.getAnnotation(this.getClass(), Auditing.class);
        if (auditing == null) return;
        Serializable id = JpaUtil.getId(this);
        if (id == null) return;
        Object o = JpaUtil.getRepository(this.getClass()).findOne(id);
        if (o == null) return;
        List<Map> result = new ArrayList<>();
        ReflectUtil.doWithFields(this.getClass(), descriptor -> {
            Class clazz = descriptor.getPropertyType();
            if (BaseModel.class.isAssignableFrom(clazz)) {
                Object ov = ReflectUtil.getFieldValue(o, descriptor);
                Object nv = ReflectUtil.getFieldValue(this, descriptor);
                String oid = ov != null ? ((BaseModel) ov).getInnerBusinessKey() : null;
                String nid = nv != null ? ((BaseModel) nv).getInnerBusinessKey() : null;
                if (oid == null && nid == null) return;
                if ( (oid !=null && oid.equals(nid)) || (nid !=null && nid.equals(oid))) return;
                Comment comment = ReflectUtil.getAnnotation(this, descriptor.getName(), Comment.class);
                String f = comment != null ? comment.value() : descriptor.getName();
                String fieldName = ReflectUtil.getField(ov == null? nv : ov, "name") != null ? "name" : "description";
                result.add(MapUtil.mapper("f", f, "o", ov == null? "" : ReflectUtil.getFieldValue(ov, fieldName) , "n", nv == null ? "" : ReflectUtil.getFieldValue(nv, fieldName)));
            } else {
                Object ov = ReflectUtil.getFieldValue(o, descriptor);
                Object nv = ReflectUtil.getFieldValue(this, descriptor);
                if (StringUtils.isEmpty(ov) && StringUtils.isEmpty(nv)) return;
                if (ov != null && ov.equals(nv)) return;
                String f;
                States states = ReflectUtil.getAnnotation(this, descriptor.getName(), States.class);
                if (states != null) {
                    f = states.name();
                    ov = getStateDescription(states, ov);
                    nv = getStateDescription(states, nv);
                } else {
                    Comment comment = ReflectUtil.getAnnotation(this, descriptor.getName(), Comment.class);
                    f = comment != null ? comment.value() : descriptor.getName();
                }
                result.add(MapUtil.mapper("f", f, "o", ov, "n", nv));
            }
        }, descriptor -> {
            Class clazz = descriptor.getPropertyType();
            if (descriptor.getName().equals("updater")) return false;
            if (ReflectUtil.getAnnotation(this, descriptor.getName(), Transient.class) != null) return false;
            if (ReflectUtil.getAnnotation(this, descriptor.getName(), ManyToOne.class) != null)  return true;
            if (ReflectUtil.getAnnotation(this, descriptor.getName(), Column.class) == null) return false;
            if (Collection.class.isAssignableFrom(clazz)) return false;
            if (BaseModel.class.isAssignableFrom(clazz)) return true;
            return ReflectUtil.isPrimitive(clazz);
        });
        if (result.size() > 0) {
            this.audit = JSON.toJSONString(result);
        }
    }

    private String getStateDescription(States states, Object stateId) {
        if (StringUtils.isEmpty(stateId)) return null;
        return Arrays.stream(states.values()).filter(state -> stateId.equals(state.id())).findFirst().get().name();
    }
}
